﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UserProfileManager.cs" company="Anton Frattaroli">
//   Copyright (c) Anton Frattaroli. All rights reserved.
// </copyright>
// <summary>
//   Defines the UserProfileManager class.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace Lidocaine.BusinessLogic
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Security.Cryptography;
    using System.Text;
    using System.Web;
    using Lidocaine.BusinessObjects;
    using Lidocaine.Data;

    /// <summary>
    /// The UserProfileManager class.
    /// </summary>
    public static class UserProfileManager
    {
        /// <summary>
        /// Gets the current user based on the logon cookie.
        /// </summary>
        public static BusinessObjects.UserProfile Current
        {
            get
            {
                int userID = 0;
                if (HttpContext.Current.Request.Cookies["Lidocaine"] != null)
                {
                    HttpCookie decoded = HttpSecureCookie.Decode(HttpContext.Current.Request.Cookies["Lidocaine"]);
                    if (Convert.ToDateTime(decoded.Values["Expires"], CultureInfo.InvariantCulture) > DateTime.Now)
                    {
                        userID = Convert.ToInt32(decoded.Values["ID"], CultureInfo.InvariantCulture);
                    }
                }

                return Data.UserProfile.Select(userID);
            }
        }

        /// <summary>
        /// Initializes a new instance of the UserProfile class.
        /// </summary>
        /// <param name="id">The identity of the database table's row referencing this user.</param>
        public static BusinessObjects.UserProfile GetUserProfile(int id)
        {
            return Data.UserProfile.Select(id);
        }

        /// <summary>
        /// Selects a list of users from the database.
        /// </summary>
        /// <returns>A read-only list of users.</returns>
        public static List<BusinessObjects.UserProfile> GetUsers()
        {
            return Data.UserProfile.SelectAll();
        }

        /// <summary>
        /// Selects a list of users from the database.
        /// </summary>
        /// <returns>A read-only list of users.</returns>
        public static Dictionary<int, string> GetUsersIdsAndUserNames()
        {
            List<BusinessObjects.UserProfile> p = Data.UserProfile.SelectAllWithoutPasswords();
            Dictionary<int, string> profiles = new Dictionary<int, string>();
            for (int i = 0; i < p.Count; i++)
            {
                profiles.Add(p[i].Id, p[i].UserName);
            }
            
            return profiles;
        }

        /// <summary>
        /// Encodes passwords with machine key configuration settings.
        /// </summary>
        /// <param name="password">The password to be encoded.</param>
        /// <returns>A byte array generated from the password.</returns>
        public static byte[] EncodePassword(string password)
        {
            MD5CryptoServiceProvider hasher = new MD5CryptoServiceProvider();
            UTF8Encoding encoder = new UTF8Encoding();
            byte[] hash = hasher.ComputeHash(encoder.GetBytes(HttpContext.Current.Server.HtmlEncode(password)));
            return hash;
        }

        /// <summary>
        /// Authenticates the user using the built-in SQL user accounts.
        /// </summary>
        /// <param name="userName">The username of the account credentials.</param>
        /// <param name="password">The password of the account credentials.</param>
        /// <returns>An instantiated Profile object if successful, null if not authenticated.</returns>
        public static BusinessObjects.UserProfile AuthenticateSql(string userName, string password)
        {
            byte[] hash = UserProfileManager.EncodePassword(password);
            int profileId = Data.UserProfile.AuthenticateSql(userName, hash);
            BusinessObjects.UserProfile profile = null;
            if (profileId != 0)
            {
                profile = Data.UserProfile.Select(profileId);
            }

            return profile;
        }

        /// <summary>
        /// Clears the current user's cookie.
        /// </summary>
        public static void LogOff()
        {
            HttpCookie decoded = new HttpCookie("Lidocaine");
            decoded.Values["ID"] = "0";
            decoded.Values["Expires"] = DateTime.Now.AddDays(-1).ToString(CultureInfo.InvariantCulture);
            decoded.Expires = DateTime.Now.AddDays(-1);
            decoded.Domain = Environment.Domain;
            HttpCookie encoded = HttpSecureCookie.Encode(decoded);
            HttpContext.Current.Response.Cookies.Set(encoded);
        }

        /// <summary>
        /// Gets a list of all Role names.
        /// </summary>
        /// <returns>A list of Role names.</returns>
        public static List<string> GetRoles(BusinessObjects.UserProfile userProfile)
        {
            return Data.Role.SelectAllNames(userProfile);
        }

        public static void ClearRoles(BusinessObjects.UserProfile userProfile)
        {
            Data.Role.DeleteRoles(userProfile);
        }

        public static void AddRole(BusinessObjects.UserProfile userProfile, BusinessObjects.Role role)
        {
            Data.Role.AddUserToRole(userProfile, role);
        }

        /// <summary>
        /// Sets an authentication cookie identifying this user.
        /// </summary>
        /// <param name="userProfile">The UserProfile that will be identified in the authentication cookie.</param>
        /// <param name="rememberMe">Adjusts the expiration date of the cookie from 1 hour to 14 days if user has chosen to be remembered.</param>
        public static void LogOn(BusinessObjects.UserProfile userProfile, bool rememberMe)
        {
            HttpCookie c = new HttpCookie("Lidocaine");
            c.Values["ID"] = userProfile.Id.ToString(CultureInfo.InvariantCulture);
            if (rememberMe)
            {
                c.Values["Expires"] = DateTime.Now.AddDays(14).ToString(CultureInfo.InvariantCulture);
            }
            else
            {
                c.Values["Expires"] = DateTime.Now.AddHours(1).ToString(CultureInfo.InvariantCulture);
            }

            c.Expires = DateTime.Now.AddYears(50);
            c.Domain = Environment.Domain;
            HttpCookie encoded = HttpSecureCookie.Encode(c);
            HttpContext.Current.Response.Cookies.Add(encoded);
        }

        /// <summary>
        /// Tests permission levels against the database.
        /// </summary>
        /// <param name="user">The user to authorize.</param>
        /// <param name="path">The HTTP request path.</param>
        /// <returns>Returns a bool value indicating whether or not the user has permissions on the path.</returns>
        public static bool HasPermission(BusinessObjects.UserProfile user, string path)
        {
            bool hasPermission = false;
            if (path.EndsWith(".SETUP", StringComparison.OrdinalIgnoreCase))
            {
                hasPermission = true;
            }
            else if (path.EndsWith(".ASHX", StringComparison.OrdinalIgnoreCase))
            {
                hasPermission = true;
            }
            else if (path.EndsWith(".PREVIEW", StringComparison.OrdinalIgnoreCase))
            {
                List<string> roles = UserProfileManager.GetRoles(UserProfileManager.Current);
                if (roles.Contains("Site Administrator") || roles.Contains("Content Manager"))
                {
                    hasPermission = true;
                }
            }
            else
            {
                string[] pathNodes = path.ToUpperInvariant().Split("/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                string reconstructedPath = string.Empty;
                List<Permission> permissionItems = null;
                for (int i = 0; i < pathNodes.Length; i++)
                {
                    permissionItems = new List<Permission>();
                    hasPermission = false;
                    if (reconstructedPath.Length == 0)
                    {
                        reconstructedPath += pathNodes[i];
                    }
                    else
                    {
                        reconstructedPath += "/" + pathNodes[i];
                    }

                    if (reconstructedPath == "WEBRESOURCE.AXD")
                    {
                        hasPermission = true;
                    }
                    else if (reconstructedPath.EndsWith(".ASPX", StringComparison.OrdinalIgnoreCase))
                    {
                        // Page not in the "CMS" directory
                        permissionItems = PageLayoutManager.GetPermissionsWithRoleInfo(reconstructedPath);
                        if (permissionItems.Count == 0)
                        {
                            HttpContext.Current.Response.Redirect(BusinessLogic.Environment.ErrorUrl + "?Code=404");
                        }

                        hasPermission = UserProfileManager.CheckPermissions(user, permissionItems);
                    }
                    else if (reconstructedPath.Contains(".") && !reconstructedPath.StartsWith("CMS/", StringComparison.OrdinalIgnoreCase) && !reconstructedPath.EndsWith(".AXD", StringComparison.OrdinalIgnoreCase))
                    {
                        // File not in the "CMS" directory
                        permissionItems = AssetManager.GetPermissionsWithRoleInfo(reconstructedPath);
                        if (permissionItems.Count == 0)
                        {
                            HttpContext.Current.Response.Redirect(BusinessLogic.Environment.ErrorUrl + "?Code=404");
                        }

                        hasPermission = UserProfileManager.CheckPermissions(user, permissionItems);
                    }
                    else if (!reconstructedPath.StartsWith("CMS/", StringComparison.OrdinalIgnoreCase) && reconstructedPath != "CMS" && !reconstructedPath.Contains("."))
                    {
                        // Directory that is not the "CMS" directory
                        permissionItems = FolderManager.GetPermissionsWithRoleInfo(reconstructedPath);
                        if (permissionItems.Count == 0)
                        {
                            HttpContext.Current.Response.Redirect(BusinessLogic.Environment.ErrorUrl + "?Code=404");
                        }

                        hasPermission = UserProfileManager.CheckPermissions(user, permissionItems);
                    }
                    else if (reconstructedPath.StartsWith("CMS/", StringComparison.OrdinalIgnoreCase) || reconstructedPath == "CMS")
                    {
                        // File in the "CMS" directory, or the "CMS" directory itself
                        List<string> roles = UserProfileManager.GetRoles(UserProfileManager.Current);
                        if (roles.Contains("Content Provider") || roles.Contains("Content Manager") || roles.Contains("Site Administrator"))
                        {
                            hasPermission = true;
                        }
                    }

                    if (!hasPermission)
                    {
                        break;
                    }
                }
            }

            return hasPermission;
        }

        /// <summary>
        /// Authorizes access for the user against the request's permissions list.
        /// </summary>
        /// <param name="user">The user to authorize.</param>
        /// <param name="permissionItems">The list of permissions for the request.</param>
        /// <returns>A boolean value indicating whether or not the user is authorized.</returns>
        private static bool CheckPermissions(BusinessObjects.UserProfile user, List<Permission> permissionItems)
        {
            bool hasPermission = false;
            for (int i = 0; i < permissionItems.Count; i++)
            {
                if (permissionItems[i].AllowType == PermissionAllowType.Role)
                {
                    if (!permissionItems[i].IsActiveDirectoryRole)
                    {
                        if (permissionItems[i].Name == "Anonymous Users")
                        {
                            hasPermission = true;
                        }
                        else if (permissionItems[i].Name == "Authenticated Users" && !user.IsAnonymous)
                        {
                            hasPermission = true;
                        }
                        else if (UserProfileManager.GetRoles(user).Contains(permissionItems[i].Name))
                        {
                            hasPermission = true;
                        }
                    }
                }
                else if (permissionItems[i].AllowId == user.Id)
                {
                    hasPermission = true;
                }
            }

            return hasPermission;
        }

        /// <summary>
        /// Inserts the user into the database.
        /// </summary>
        /// <returns>The inserted UserProfile with a newly populated Id field.</returns>
        public static BusinessObjects.UserProfile SaveAs(BusinessObjects.UserProfile userProfile)
        {
            return Data.UserProfile.Insert(userProfile);
        }

        /// <summary>
        /// Updates the user's database fields.
        /// </summary>
        public static void Save(BusinessObjects.UserProfile userProfile)
        {
            Data.UserProfile.Update(userProfile);
        }

        /// <summary>
        /// Deletes the user's database fields.
        /// </summary>
        public static void Delete(BusinessObjects.UserProfile userProfile)
        {
            Data.UserProfile.Delete(userProfile);
        }
    }
}